package mspbots.cw.client.core.data.users;

import cn.hutool.core.collection.ListUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.log4j.Log4j2;
import mspbots.cw.client.core.AbstractToolsUtil;
import mspbots.cw.client.core.data.BaseApiService;
import mspbots.cw.client.core.app.SyncRequest;
import mspbots.cw.client.core.client.ClientProperties;
import mspbots.cw.common.*;
import mspbots.cw.common.callback.CallbackAction;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * com.mspbots.sync.scheduled.service.UserService
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2019/6/11
 */
@Log4j2
@Service
public class UsersServiceImpl extends AbstractToolsUtil implements BaseApiService {


    private Mono<JsonNode> count(ClientProperties properties, QueryParams queryParams) {
        return super.cwWebClient.get("/system/members/count", queryParams)
                .contextWrite(ctx -> ctx.put("properties", properties));
    }

    @Override
    public Flux<JsonNode> find(Long tenantId, QueryParams queryParams) {
        ResultSync resultSync = ResultSync.withDefault("USERS-SEARCH");
        return this.loadProperties(tenantId)
                .flatMapMany(properties -> this.get(properties, queryParams)
                        .contextWrite(ctx -> ctx.put("resultSync", resultSync)));
    }

    @Override
    public Mono<JsonNode> count(Long tenantId, QueryParams queryParams) {
        ResultSync resultSync = ResultSync.withDefault("USERS-COUNT");
        return super.loadProperties(tenantId)
                .flatMap(clientProperties -> this.count(clientProperties, queryParams))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync));
    }

    @Override
    public Mono<Void> syncFast(ClientProperties properties) {
        ResultSync resultSync = ResultSync.withDefault("USERS-SYNC");
        return this.readAnchorTimeLong(RedisKeys.USERS_ANCHOR_REDIS_KEY_PREFIX + properties.getTenantId())
                .map(sysTime -> QueryParams.withDefault()
                        .setConditions("lastUpdated>[" + sysTime.format(DateTimeFormatter.ISO_DATE_TIME) + "]")
                        .setOrderBy("lastUpdated asc"))
                .flatMapMany(params -> this.get(properties, params))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .doOnNext(dataNode -> this.syncResult(properties.getTenantId(),
                        resultSync.getRequestId(), dataNode))
                .then();
    }

    @Override
    public Mono<Void> manualSync(ClientProperties properties, SyncRequest syncRequest) {
        ResultSync resultSync = ResultSync.withDefault("MANUAL-USERS-SYNC");
        String conditionsStr = "lastUpdated>=[" + syncRequest.getBeginTime().format(DateTimeFormatter.ISO_DATE_TIME) +
                "] and lastUpdated <=[" + syncRequest.getEndTime().format(DateTimeFormatter.ISO_DATE_TIME) + "]";
        return this.get(properties, QueryParams.withDefault().setConditions(conditionsStr)
                .setOrderBy("lastUpdated asc"))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .doOnNext(node -> this.syncResult(properties.getTenantId(),
                        resultSync.getRequestId(), node))
                .then();
    }

    private void syncResult(Long tenantId, UUID id, JsonNode resultNode) {
        this.sendEntity(RabbitKeys.USERS_SYNC_QUEUE_KEY, ResultMsg.builder()
                .requestId(id).id(resultNode.get("id").longValue())
                .action(CallbackAction.none.name()).entity(resultNode).tenantId(tenantId)
                .syncDate(LocalDateTime.now()).build());

        this.recordAnchorTimeLong(RedisKeys.USERS_ANCHOR_REDIS_KEY_PREFIX + tenantId,
                resultNode.findValue("lastUpdated").textValue());
    }

    private Flux<JsonNode> get(ClientProperties properties, QueryParams queryParams) {
        return this.cwWebClient.fetch("/system/members", queryParams)
                .flatMap(this::buildUsers)
                .contextWrite(ctx -> ctx.put("properties", properties));
    }

    private Mono<JsonNode> buildUsers(JsonNode entryNode) {
        ObjectNode userNode = entryNode.deepCopy();
        return Mono.just(userNode)
                .flatMap(this::setSkills)
                .flatMap(this::setTeams);
    }

    private Mono<ObjectNode> setSkills(ObjectNode entryNode) {
        ResultSync resultSync = ResultSync.withDefault("USERS-SKILLS-SYNC");
        return super.cwWebClient.fetch("/system/members/" + entryNode.get("id").longValue() + "/skills",
                QueryParams.withDefault())
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .collectList()
                .map(skillsList -> entryNode.putPOJO("skills",
                        this.objectMapper.convertValue(skillsList, ArrayNode.class)))
                .onErrorReturn(entryNode)
                .defaultIfEmpty(entryNode);
    }

    private Mono<ObjectNode> setTeams(ObjectNode entryNode) {
        var teamIds = this.objectMapper.convertValue(entryNode.get("serviceBoardTeamIds"), List.class);
        if (ObjectUtils.isEmpty(teamIds) || teamIds.size() == 0) {
            return Mono.just(entryNode);
        }
        int pageSize = 5;
        int pageCount;
        if (teamIds.size() % pageSize == 0) {
            pageCount = teamIds.size() / pageSize;
        } else {
            pageCount = teamIds.size() / pageSize + 1;
        }
        List<Mono<JsonNode>> monoList = new ArrayList<>();
        for (int x = 0; x < pageCount; x++) {
            String idsStr = StringUtils.collectionToCommaDelimitedString(ListUtil.page(0, pageSize, teamIds));
            var teamsMono = super.cwWebClient.get("/service/teams", QueryParams.withDefault()
                    .setConditions("id in (" + idsStr + ")"))
                    .onErrorResume(err -> Mono.empty());
            monoList.add(teamsMono);
        }
        return Flux.concat(monoList)
                .collectList()
                .map(teams -> entryNode.putPOJO("teamNames", teams))
                .onErrorReturn(entryNode)
                .defaultIfEmpty(entryNode);

    }
}
